{$CLEO .cs}
{$USE CLEO+}

SCRIPT_NAME 'OILCORE'

//***********************************************************************************//
///////////////////////////////////////////////////////////////////////////////////////
// Oil Core System
// Author: AlvarynGTA
///////////////////////////////////////////////////////////////////////////////////////
//
// Central runtime controller for the oil puddle system.
//
// Responsibilities:
// - owns shared runtime buffers
// - receives external oil spawn/grow requests
// - creates streamed oil puddle instances
// - grows existing nearby puddles
// - enforces global active puddle limits
// - handles bullet impact ignition requests
// - writes the public request buffer pointer to OilProperties.ini
//
// Public request API:
// External scripts write into OilRequestPtr.
//
// OilRequestPtr layout:
// 0x00 = request XYZ      FLOAT[3]
// 0x0C = ownerChar        INT
// 0x10 = request flag     INT
//
// ownerChar:
// Ped handle used for native fire ownership attribution.
// Use -1 for non-ped, vehicle leak, object leak, or ambient oil sources.
//
// Shared runtime systems:
// - dropsList: active puddle @DROP_DATA registry
// - activeDropsPtr: global active puddle counter
// - igniteBufferPtr: shared external ignition XYZ buffer
// - oilConfigPtr: shared read-only config buffer for puddle instances
//
// Runtime flow:
// request received -> grow nearby puddle or create new puddle
// bullet impact -> write ignition XYZ -> puddle instances consume it
// max active drops reached -> oldest listed puddle is removed from registry
// removed puddles self-cleanup during their validation pass
//
// Notes:
// Runtime pointers are generated on script startup and are valid only after OilCore initializes.
// Every puddle is a streamed script instance, so MaxActiveDrops directly affects runtime load.
//
//***********************************************************************************//

CONST
    // -------------------------------------------------------------------------
    // Oil request buffer layout
    // Used by: OilCore, PetrolCanWeapon
    // -------------------------------------------------------------------------
    REQ_POS_XYZ = 0x00        // OilCore reads / Weapon writes request position
    REQ_OWNER_CHAR = 0x0C     // OilCore reads / Weapon writes ped owner handle
    REQ_FLAG = 0x10           // OilCore reads-clears / Weapon writes request trigger

    // -------------------------------------------------------------------------
    // Ignite buffer layout
    // Used by: OilCore, OilPuddleDrop
    // -------------------------------------------------------------------------
    IGNITE_POS_XYZ = 0x00     // OilCore writes / OilPuddleDrop reads-clears external ignition position

    // -------------------------------------------------------------------------
    // oilConfigPtr layout
    // Written by: OilCore
    // Read by: OilPuddleDrop
    // Lifetime values also read by: OilCore GET_FIRE_LIFETIME
    // -------------------------------------------------------------------------
    CFG_DROP_IDLE_TIME = 0x00          // OilPuddleDrop: idle expiration timing
    CFG_DROP_CLEANUP_RADIUS = 0x04     // OilPuddleDrop: cleanup distance from focused active player

    CFG_DROP_INTENSITY = 0x08          // OilPuddleDrop: shadow intensity / fade base
    CFG_SHADOW_RED = 0x0C              // OilPuddleDrop: DRAW_SHADOW red
    CFG_SHADOW_GREEN = 0x10            // OilPuddleDrop: DRAW_SHADOW green
    CFG_SHADOW_BLUE = 0x14             // OilPuddleDrop: DRAW_SHADOW blue

    CFG_FIRE_PROPAGATION_EXTRA = 0x18  // OilPuddleDrop: external fire detection extra radius
    CFG_FIRE_INTERSECTION_EXTRA = 0x1C // OilPuddleDrop: burning puddle intersection tolerance
    CFG_FIRE_TOUCH_Z_SCALE = 0x20      // OilPuddleDrop: vertical fire detection tolerance scale
    CFG_PROPAGATION_DELAY = 0x24       // OilPuddleDrop: pending fire delay

    CFG_FIRE_BASE_LIFETIME = 0x28      // OilCore: fire lifetime calculation
    CFG_FIRE_LIFETIME_SCALE = 0x2C     // OilCore: fire lifetime calculation
    CFG_FIRE_MAX_LIFETIME = 0x30       // OilCore: fire lifetime clamp

    // -------------------------------------------------------------------------
    // @DROP_DATA layout
    // Written by: OilPuddleDrop
    // Read/updated by: OilCore, OilPuddleDrop
    // -------------------------------------------------------------------------
    DROP_POS_XYZ = 0x00                // OilCore reads / OilPuddleDrop writes position
    DROP_TARGET_SIZE = 0x0C            // OilCore reads-updates / OilPuddleDrop reads visual size target
    DROP_FIRE_RADIUS = 0x10            // OilCore reads-updates / OilPuddleDrop reads ignition/fire radius
    DROP_IS_BURNING = 0x14             // OilCore reads / OilPuddleDrop writes burning state
    DROP_FIRE_LIFETIME = 0x18          // OilCore updates / OilPuddleDrop reads fire lifetime
END

// Shared runtime handles
INT oilRequestPtr
INT dropsList, activeDropsPtr, igniteBufferPtr, oilConfigPtr

// Config values
INT maxActiveDrops
FLOAT dropMinSize, dropInitialMaxSize
FLOAT dropGrowAmount, dropMaxSize
FLOAT dropFireRadiusExtra, dropEffectiveRadiusFactor

// Request runtime
INT ownerChar, requestFlag
FLOAT x, y, z

// Shared scan runtime
INT scanListSize, scanIndex
INT scannedDropPtr, scannedDropBurning
FLOAT scannedDropX, scannedDropY, scannedDropZ

// Bullet impact event runtime
INT bulletImpactWeaponType, bulletImpactColPoint
FLOAT bulletImpactX, bulletImpactY, bulletImpactZ

// Reusable temps
INT tempInt
FLOAT tempFloat, tempFloat2

//***********************************************************************************//

CREATE_LIST 0 dropsList

GET_LABEL_POINTER @ACTIVE_DROPS_COUNT activeDropsPtr
WRITE_MEMORY activeDropsPtr 4 0 FALSE

GET_LABEL_POINTER @EXTERNAL_CREATE_FIRE igniteBufferPtr
WRITE_STRUCT_OFFSET_MULTI igniteBufferPtr IGNITE_POS_XYZ 3 4 0.0 0.0 0.0

GET_LABEL_POINTER @OIL_REQUEST_BUFFER oilRequestPtr
WRITE_STRUCT_OFFSET_MULTI oilRequestPtr REQ_POS_XYZ 3 4 0.0 0.0 0.0
WRITE_STRUCT_OFFSET oilRequestPtr REQ_OWNER_CHAR 4 -1
WRITE_STRUCT_OFFSET oilRequestPtr REQ_FLAG 4 FALSE

GET_LABEL_POINTER @OIL_CONFIG_BUFFER oilConfigPtr

GOSUB @read_INI

WRITE_INT_TO_INI_FILE oilRequestPtr "CLEO\OilProperties.ini" "RuntimeMemory" "OilRequestPtr"

SET_SCRIPT_EVENT_BULLET_IMPACT 1 @on_bullet_impact_EVENT scannedDropPtr scannedDropPtr bulletImpactWeaponType bulletImpactColPoint

//-----------------------------------------------------------------------------------//
///////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------//

//-----------------------------------------------------------------------------//
:MAIN_LOOP
//-----------------------------------------------------------------------------//
WAIT 0

requestFlag = READ_STRUCT_OFFSET oilRequestPtr REQ_FLAG 4

IF requestFlag == TRUE
THEN
    READ_STRUCT_OFFSET_MULTI oilRequestPtr REQ_POS_XYZ 3 4 x y z
    ownerChar = READ_STRUCT_OFFSET oilRequestPtr REQ_OWNER_CHAR 4

    GOSUB @oil_try_grow_or_create

    WRITE_STRUCT_OFFSET oilRequestPtr REQ_FLAG 4 FALSE
END

JUMP @MAIN_LOOP

//-----------------------------------------------------------------------------//
:on_bullet_impact_EVENT
//-----------------------------------------------------------------------------//
scanListSize = GET_LIST_SIZE dropsList

IF AND
    scanListSize > 0
    bulletImpactWeaponType > 0
THEN
    GET_COLPOINT_COORDINATES bulletImpactColPoint bulletImpactX bulletImpactY bulletImpactZ

    scanIndex = 0
    WHILE scanIndex < scanListSize
        scannedDropPtr = GET_LIST_VALUE_BY_INDEX dropsList scanIndex

        scannedDropBurning = READ_STRUCT_OFFSET scannedDropPtr DROP_IS_BURNING 4

        IF scannedDropBurning == FALSE
        THEN
            READ_STRUCT_OFFSET_MULTI scannedDropPtr DROP_POS_XYZ 3 4 scannedDropX scannedDropY scannedDropZ

            tempFloat = GET_DISTANCE_BETWEEN_COORDS_3D scannedDropX scannedDropY scannedDropZ bulletImpactX bulletImpactY bulletImpactZ
            tempFloat2 = READ_STRUCT_OFFSET scannedDropPtr DROP_FIRE_RADIUS 4

            IF tempFloat < tempFloat2
            THEN
                WRITE_STRUCT_OFFSET_MULTI igniteBufferPtr IGNITE_POS_XYZ 3 4 scannedDropX scannedDropY scannedDropZ
                RETURN_SCRIPT_EVENT
            END
        END

        scanIndex += 1
    END
END

RETURN_SCRIPT_EVENT

//-----------------------------------------------------------------------------------//
///////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------//

//_______________________________________________________________
:oil_try_grow_or_create
//_______________________________________________________________
tempInt = FALSE

scanListSize = GET_LIST_SIZE dropsList
scanIndex = 0

WHILE scanIndex < scanListSize
    scannedDropPtr = GET_LIST_VALUE_BY_INDEX dropsList scanIndex

    scannedDropBurning = READ_STRUCT_OFFSET scannedDropPtr DROP_IS_BURNING 4

    IF scannedDropBurning == FALSE
    THEN
        READ_STRUCT_OFFSET_MULTI scannedDropPtr DROP_POS_XYZ 3 4 scannedDropX scannedDropY scannedDropZ

        tempFloat = GET_DISTANCE_BETWEEN_COORDS_3D x y z scannedDropX scannedDropY scannedDropZ

        tempFloat2 = READ_STRUCT_OFFSET scannedDropPtr DROP_TARGET_SIZE 4
        tempFloat2 *= dropEffectiveRadiusFactor

        IF tempFloat < tempFloat2
        THEN
            tempFloat2 = READ_STRUCT_OFFSET scannedDropPtr DROP_TARGET_SIZE 4

            IF tempFloat2 < dropMaxSize
            THEN
                tempFloat2 += dropGrowAmount

                IF tempFloat2 > dropMaxSize
                THEN
                    tempFloat2 = dropMaxSize
                END

                WRITE_STRUCT_OFFSET scannedDropPtr DROP_TARGET_SIZE 4 tempFloat2

                tempFloat2 *= dropEffectiveRadiusFactor
                tempFloat2 += dropFireRadiusExtra

                WRITE_STRUCT_OFFSET scannedDropPtr DROP_FIRE_RADIUS 4 tempFloat2

                tempInt = GET_FIRE_LIFETIME(tempFloat2, oilConfigPtr)
                WRITE_STRUCT_OFFSET scannedDropPtr DROP_FIRE_LIFETIME 4 tempInt

                tempInt = TRUE
                scanIndex = scanListSize
            END
        END
    END

    scanIndex += 1
END

IF tempInt == FALSE
THEN
    GOSUB @oil_create_drop
END

RETURN

//_______________________________________________________________
:oil_create_drop
//_______________________________________________________________
tempInt = READ_MEMORY activeDropsPtr 4 FALSE

IF tempInt >= maxActiveDrops
THEN
    scannedDropPtr = GET_LIST_VALUE_BY_INDEX dropsList 0
    LIST_REMOVE_VALUE dropsList scannedDropPtr
END

tempFloat = GENERATE_RANDOM_FLOAT_IN_RANGE dropMinSize dropInitialMaxSize

tempFloat2 = tempFloat
tempFloat2 *= dropEffectiveRadiusFactor
tempFloat2 += dropFireRadiusExtra

tempInt = GET_FIRE_LIFETIME(tempFloat2, oilConfigPtr)

// OilPuddleDrop.s(
//     FLOAT posX,
//     FLOAT posY,
//     FLOAT posZ,
//     INT ownerChar,
//     FLOAT visualSize,
//     FLOAT fireRadius,
//     INT fireLifeTotal,
//
//     INT dropsList,
//     INT igniteBufferPtr,
//     INT activeDropsPtr,
//     INT oilConfigPtr
// )
STREAM_CUSTOM_SCRIPT "OilPuddleDrop.s" x y z ownerChar tempFloat tempFloat2 tempInt dropsList igniteBufferPtr activeDropsPtr oilConfigPtr

RETURN

//-----------------------------------------------------------------------------------//
///////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------//

//_______________________________________________________________
FUNCTION GET_FIRE_LIFETIME(fireRadius: FLOAT, configPtr: INT): INT
    INT fireLife, maxLifetime
    FLOAT calcLife, lifetimeScale

    fireLife = READ_STRUCT_OFFSET configPtr CFG_FIRE_BASE_LIFETIME 4
    lifetimeScale = READ_STRUCT_OFFSET configPtr CFG_FIRE_LIFETIME_SCALE 4
    maxLifetime = READ_STRUCT_OFFSET configPtr CFG_FIRE_MAX_LIFETIME 4

    calcLife =# fireLife
    calcLife *= fireRadius
    calcLife *= lifetimeScale

    fireLife =# calcLife

    IF fireLife < 100
    THEN fireLife = 100
    END

    IF fireLife > maxLifetime
    THEN fireLife = maxLifetime
    END

    CLEO_RETURN 1 fireLife
END

//-----------------------------------------------------------------------------------//
///////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------//

//=========================================================================//
:read_INI
//=========================================================================//
IF NOT DOES_FILE_EXIST "CLEO\OilProperties.ini"
THEN
    PRINT_STRING_NOW "~r~OilProperties.ini missing" 5000
    TERMINATE_THIS_CUSTOM_SCRIPT
END

// --------------------------
// SETTINGS
// --------------------------
IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Settings" "EnableOilSystem" tempInt
THEN tempInt = 0
END

IF tempInt <> 1
THEN TERMINATE_THIS_CUSTOM_SCRIPT
END

// --------------------------
// DROP LIMIT
// --------------------------
IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "MaxActiveDrops" maxActiveDrops
THEN maxActiveDrops = 150
END

IF maxActiveDrops < 1
THEN maxActiveDrops = 1
END

IF maxActiveDrops > 500
THEN maxActiveDrops = 500
END

// --------------------------
// DROP LIFETIME
// --------------------------
IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "IdleTime" tempInt
THEN tempInt = 300000
END

IF tempInt < 1000
THEN tempInt = 1000
END

IF tempInt > 1800000
THEN tempInt = 1800000
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_DROP_IDLE_TIME 4 tempInt

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "CleanupRadius" tempFloat
THEN tempFloat = 100.0
END

IF tempFloat < 10.0
THEN tempFloat = 10.0
END

IF tempFloat > 500.0
THEN tempFloat = 500.0
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_DROP_CLEANUP_RADIUS 4 tempFloat

// --------------------------
// DROP APPEARANCE
// --------------------------
IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "Intensity" tempInt
THEN tempInt = 140
END

IF tempInt < 1
THEN tempInt = 1
END

IF tempInt > 255
THEN tempInt = 255
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_DROP_INTENSITY 4 tempInt

IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "ShadowRed" tempInt
THEN tempInt = 0
END

IF tempInt < 0
THEN tempInt = 0
END

IF tempInt > 255
THEN tempInt = 255
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_SHADOW_RED 4 tempInt

IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "ShadowGreen" tempInt
THEN tempInt = 0
END

IF tempInt < 0
THEN tempInt = 0
END

IF tempInt > 255
THEN tempInt = 255
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_SHADOW_GREEN 4 tempInt

IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Drop" "ShadowBlue" tempInt
THEN tempInt = 0
END

IF tempInt < 0
THEN tempInt = 0
END

IF tempInt > 255
THEN tempInt = 255
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_SHADOW_BLUE 4 tempInt

// --------------------------
// INITIAL SIZE
// --------------------------
IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Size" "MinSize" dropMinSize
THEN dropMinSize = 0.15
END

IF dropMinSize < 0.05
THEN dropMinSize = 0.05
END

IF dropMinSize > 5.0
THEN dropMinSize = 5.0
END

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Size" "InitialMaxSize" dropInitialMaxSize
THEN dropInitialMaxSize = 0.30
END

IF dropInitialMaxSize < 0.05
THEN dropInitialMaxSize = 0.05
END

IF dropInitialMaxSize > 5.0
THEN dropInitialMaxSize = 5.0
END

IF dropInitialMaxSize < dropMinSize
THEN dropInitialMaxSize = dropMinSize
END

// --------------------------
// GROWTH
// --------------------------
IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Size" "GrowAmount" dropGrowAmount
THEN dropGrowAmount = 0.075
END

IF dropGrowAmount < 0.001
THEN dropGrowAmount = 0.001
END

IF dropGrowAmount > 1.0
THEN dropGrowAmount = 1.0
END

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Size" "MaxSize" dropMaxSize
THEN dropMaxSize = 1.50
END

IF dropMaxSize < 0.10
THEN dropMaxSize = 0.10
END

IF dropMaxSize > 10.0
THEN dropMaxSize = 10.0
END

IF dropMaxSize < dropInitialMaxSize
THEN dropMaxSize = dropInitialMaxSize
END

// --------------------------
// EFFECTIVE RADIUS
// --------------------------
IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Size" "EffectiveRadiusFactor" dropEffectiveRadiusFactor
THEN dropEffectiveRadiusFactor = 0.90
END

IF dropEffectiveRadiusFactor < 0.10
THEN dropEffectiveRadiusFactor = 0.10
END

IF dropEffectiveRadiusFactor > 2.0
THEN dropEffectiveRadiusFactor = 2.0
END

// --------------------------
// IGNITION RADIUS
// --------------------------
IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "RadiusExtra" dropFireRadiusExtra
THEN dropFireRadiusExtra = 0.15
END

IF dropFireRadiusExtra < 0.0
THEN dropFireRadiusExtra = 0.0
END

IF dropFireRadiusExtra > 5.0
THEN dropFireRadiusExtra = 5.0
END

// --------------------------
// FIRE PROPAGATION
// --------------------------
IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "PropagationExtra" tempFloat
THEN tempFloat = 0.35
END

IF tempFloat < 0.0
THEN tempFloat = 0.0
END

IF tempFloat > 10.0
THEN tempFloat = 10.0
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_PROPAGATION_EXTRA 4 tempFloat

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "IntersectionExtra" tempFloat
THEN tempFloat = 0.10
END

IF tempFloat < 0.0
THEN tempFloat = 0.0
END

IF tempFloat > 5.0
THEN tempFloat = 5.0
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_INTERSECTION_EXTRA 4 tempFloat

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "FireTouchZScale" tempFloat
THEN tempFloat = 0.50
END

IF tempFloat < 0.05
THEN tempFloat = 0.05
END

IF tempFloat > 2.0
THEN tempFloat = 2.0
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_TOUCH_Z_SCALE 4 tempFloat

IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "PropagationDelay" tempInt
THEN tempInt = 150
END

IF tempInt < 10
THEN tempInt = 10
END

IF tempInt > 5000
THEN tempInt = 5000
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_PROPAGATION_DELAY 4 tempInt

// --------------------------
// FIRE LIFETIME
// --------------------------
IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "BaseLifetime" tempInt
THEN tempInt = 10000
END

IF tempInt < 100
THEN tempInt = 100
END

IF tempInt > 120000
THEN tempInt = 120000
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_BASE_LIFETIME 4 tempInt

IF NOT READ_FLOAT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "LifetimeScale" tempFloat
THEN tempFloat = 1.0
END

IF tempFloat < 0.1
THEN tempFloat = 0.1
END

IF tempFloat > 10.0
THEN tempFloat = 10.0
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_LIFETIME_SCALE 4 tempFloat

IF NOT READ_INT_FROM_INI_FILE "CLEO\OilProperties.ini" "Fire" "MaxLifetime" tempInt
THEN tempInt = 25000
END

IF tempInt < 1000
THEN tempInt = 1000
END

IF tempInt > 300000
THEN tempInt = 300000
END

WRITE_STRUCT_OFFSET oilConfigPtr CFG_FIRE_MAX_LIFETIME 4 tempInt

RETURN

//-----------------------------------------------------------------------------------//
///////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------//

//=========================================================================//
:EXTERNAL_CREATE_FIRE
//=========================================================================//
HEX
    00 (12)
END

//=========================================================================//
:ACTIVE_DROPS_COUNT
//=========================================================================//
HEX
    00 00 00 00
END

//=========================================================================//
:OIL_REQUEST_BUFFER
//=========================================================================//
HEX
    00 (20)
END

//=========================================================================//
:OIL_CONFIG_BUFFER
//=========================================================================//
HEX
    00 (52)
END